home *** CD-ROM | disk | FTP | other *** search
/ Inside Mac Games Volume 1 / Inside Mac Games Volume 1.iso / Shareware / Arcade / Bolo 0.99.2 / Documentation / Sample Code / Standard Autopilot sample code / Standard Autopilot.c < prev    next >
Encoding:
Text File  |  1993-05-23  |  33.5 KB  |  1,052 lines  |  [TEXT/KAHL]

  1. // ****************************************************************************
  2. //
  3. // Bolo standard Autopilot Brain
  4. // (C) 1993 Stuart Cheshire <cheshire@cs.stanford.edu>
  5. // I make no claims that this is good code. It is provided solely as
  6. // simple example code to assist in writing Bolo plug-in Brain modules.
  7. // I do not have the time to tidy it up and make it elegant at all, so
  8. // if you have ever been a student of mine and lost marks for bad style,
  9. // don't even think of trying to use this code to justify claiming extra
  10. // marks. It is crap. And it uses far too many global variables.
  11. //
  12. // ****************************************************************************
  13.  
  14. #include <FixMath.h>
  15. #include "debug.h"
  16. #include "Brain.h"
  17.  
  18. // ****************************************************************************
  19.  
  20. local const BrainInfo *info;
  21. local long ThinkStart;
  22. local MAP_X nearx;
  23. local MAP_Y neary;
  24. local MAP_X tankleftx, tankrightx;
  25. local MAP_Y tanktopy, tankbottomy;
  26. local Boolean tank_close_to_mine;
  27. local Boolean still_on_boat;    // Set until tank gets off boat
  28. local BYTE land_direction;        // Initial guess at where the land is
  29.  
  30. // If cannot reach a target, get bored and ignore it after 20 seconds
  31. #define ATTENTION_SPAN (60*20)
  32. #define MAX_VIS_RANGE 0x1000
  33. #define DEFENSIVE_RANGE 0xA00
  34.  
  35. // Keyboard Control variables
  36. local u_long keys, taps, last_keys, last_taps;
  37.  
  38. // Menu handling variables
  39. #define MyMenuID 1000
  40. local MenuHandle MyMenu;
  41. local Boolean aggressive = FALSE, laymines = TRUE;
  42. local Boolean clearmines = TRUE, placepills = TRUE;
  43.  
  44. // Thinking variables
  45. local Boolean shootit, runaway;
  46. local BYTE chosen_gunrange;        // Desired gun range, for shooting mines
  47. local BYTE shoot_direction;        // to aim the gun accurately
  48. local BYTE shoot_dir_vote;        // shoot_direction converted to range 0-15
  49. local BYTE chosen_direction;
  50. local BYTE flying_shells;        // total number of shells in flight at the moment
  51. local BYTE incoming_shells;        // total number of shells heading towards us
  52. local long direction_votes[16];
  53. local u_long target_distances[16];
  54.  
  55. local long boredomtime = ATTENTION_SPAN;
  56. local long *tankboredom, *pillboredom, *baseboredom;
  57.  
  58. local u_long current_dist = MAX_VIS_RANGE;
  59. local long current_attempt_time;
  60.  
  61. local MAP_X scoutx;
  62. local MAP_Y scouty;
  63. #define CAN_DO_BUILDING (info->build->action == 0 && info->man_status == 0 \
  64.                         && !info->inboat && flying_shells == 0 && !shootit)
  65.  
  66. typedef struct { char x; char y; } charpair;
  67.  
  68. typedef union
  69.     {
  70.     charpair s25[25];
  71.     struct { charpair s1[1]; charpair s8[8]; charpair s16[16]; } s;
  72.     } SURROUNDING_SQUARES;
  73.  
  74. local const SURROUNDING_SQUARES surrounding =
  75.     {
  76.     0,0,
  77.     0,-1, 1,-1, 1,0, 1,1, 0,1, -1,1, -1,0, -1,-1,
  78.     0,-2, 1,-2, 2,-2, 2,-1, 2,0, 2,1, 2,2, 1,2,
  79.     0,2, -1,2, -2,2, -2,1, -2,0, -2,-1, -2,-2, -1, -2
  80.     };
  81.  
  82. // ****************************************************************************
  83.  
  84. // General utility routines
  85.  
  86. #define CanSee(X,Y) \
  87.     ((X) >= info->view_left && (X) < info->view_left + info->view_width && \
  88.      (Y) >= info->view_top  && (Y) < info->view_top  + info->view_height )
  89.  
  90. local TERRAIN raw_getmapcell(MAP_X x, MAP_Y y)
  91.     {
  92.     if (x < info->view_left || y < info->view_top) return(DEEPSEA);
  93.     y -= info->view_top;
  94.     x -= info->view_left;
  95.     if (x >= info->view_width || y >= info->view_height) return(DEEPSEA);
  96.     return(info->viewdata[y*info->view_width + x]);
  97.     }
  98.  
  99. // These two read the terrain / check for mine at MAP coordinates X,Y
  100. #define getmapcellM(X,Y) (raw_getmapcell((X),(Y)) & TERRAIN_MASK)
  101. #define checkmineM(X,Y)  (raw_getmapcell((X),(Y)) & TERRAIN_MINE)
  102.  
  103. // These two read the terrain / check for mine at WORLD coordinates X,Y
  104. #define getmapcellW(X,Y) getmapcellM((X)>>8, (Y)>>8)
  105. #define checkmineW(X,Y)  checkmineM((X)>>8, (Y)>>8)
  106.  
  107. local u_long findrange(WORLD_X x1, WORLD_Y y1, WORLD_X x2, WORLD_Y y2)
  108.     {
  109.     register long xdiff = (long)x1 - (long)x2;
  110.     register long ydiff = (long)y1 - (long)y2;
  111.     return(FracSqrt(xdiff*xdiff + ydiff*ydiff) >> 15);
  112.     }
  113.  
  114. #define objectrange_to_me(OB) \
  115.         findrange((OB)->x, (OB)->y, info->tankx, info->tanky)
  116.  
  117. // For Mac fixed point trig routines, full circle is 2 * PI * 0x10000
  118. // We want full circle to be 0x100, so divide by (2 * PI * 0x10000 / 0x100)
  119. // which is 1608 in decimal
  120. #define aim(X,Y) ((FixATan2(-(Y),(X))/1608) & 0xFF)
  121.  
  122. // sin and cos routines are adjusted to take angles in the range 0-255
  123. // and return return values in the range +/-128
  124. #define sin(X) ((short)(FracSin((u_char)(X)*1608L) >> 23))
  125. #define cos(X) ((short)(FracCos((u_char)(X)*1608L) >> 23))
  126.  
  127. // ****************************************************************************
  128.  
  129. // Route finding routines
  130.  
  131. typedef struct            // Used to find best route through cost array
  132.     {
  133.     BYTE x;                // Location in array
  134.     BYTE y;
  135.     u_short cost;        // Cost to reach this square
  136.     } CostPoint;
  137.  
  138. #define COST_SEARCH_RANGE 14
  139. // Tank can see the square it is on, and up to COST_SEARCH_RANGE
  140. // above, below and to either side of it
  141. #define COST_ARRAY_SIZE (COST_SEARCH_RANGE + 1 + COST_SEARCH_RANGE)
  142. typedef u_short u_short32[32];                    // Row of 32 values
  143. local u_short32 squarecost[COST_ARRAY_SIZE];    // Array of rows of cost values
  144. #define UNKNOWN_COST 0xFFFF
  145. #define MAX_COST (UNKNOWN_COST-1)
  146. local MAP_X costarray_left;        // position of array on map
  147. local MAP_Y costarray_top;
  148. local MAP_X costtarget_x;        // position of target on map
  149. local MAP_Y costtarget_y;
  150. local CostPoint subtarget;        // target, or closest point if target is outside costarray
  151. local long costarray_time;        // time array was created
  152. local BYTE costarray_boat;        // Whether costarray calculated for boat or tank
  153. local BYTE ca_tankx, ca_tanky;    // Position of tank in costarray coordinates
  154.  
  155. #define MAX_HEAP_SIZE 200
  156. local CostPoint cost_heap[MAX_HEAP_SIZE];        // Sorted heap of CostPoints
  157. local short heap_size=0;
  158.  
  159. local void addtoheap(int place, CostPoint new)
  160.     {
  161.     int parent = (place-1)>>1;
  162.     cost_heap[place]=new;
  163.     while (place>0 && cost_heap[parent].cost > cost_heap[place].cost)
  164.         {
  165.         CostPoint temp    = cost_heap[place ];
  166.         cost_heap[place ] = cost_heap[parent];
  167.         cost_heap[parent] = temp;
  168.         place=parent;
  169.         parent=(place-1)>>1;
  170.         }
  171.     }
  172.  
  173. local CostPoint removefromheap(void)
  174.     {
  175.     CostPoint top = cost_heap[0];            // extract top element
  176.     int gap   = 0;                            // top is now empty
  177.     int left  = 1;                            // Left child is element 1
  178.     int right = 2;                            // Right child is element 2
  179.     heap_size--;                            // We have removed one element
  180.     while(left<=heap_size)                            // move gap to bottom
  181.         {
  182.         if(right>heap_size || cost_heap[right].cost > cost_heap[left].cost)
  183.             { cost_heap[gap]=cost_heap[left ]; gap=left;  }
  184.         else{ cost_heap[gap]=cost_heap[right]; gap=right; }
  185.         left  = (gap<<1)+1;
  186.         right = (gap<<1)+2;
  187.         }
  188.     if(gap != heap_size) addtoheap(gap,cost_heap[heap_size]);
  189.     return(top);                            // return extracted element
  190.     }
  191.  
  192. local void setcost(CostPoint c)
  193.     {
  194.     Boolean already_present = (squarecost[c.y][c.x] < UNKNOWN_COST);
  195.     squarecost[c.y][c.x] = c.cost;
  196.     
  197.     if (already_present)
  198.         {
  199.         int i;
  200.         //DebugStr("\pUpdating location already in heap");
  201.         for (i=0; i<heap_size; i++)
  202.             if (cost_heap[i].x == c.x && cost_heap[i].y == c.y) break;
  203.         //if (i >= heap_size) DebugStr("\pFailed to find in heap");
  204.         addtoheap(i, c);
  205.         }
  206.     // If heap not full add new element
  207.     else if (heap_size < MAX_HEAP_SIZE) addtoheap(heap_size++, c);
  208.     // else overwrite last element in heap
  209.     else addtoheap(heap_size-1, c);
  210.     }
  211.  
  212. // Costs are roughly related to speed, but biased for other properties
  213. // eg Water is very bad because it loses shells, and forest is nicer than
  214. // its speed would suggest because of its cloaking property.
  215. local u_short route_square_costs[NUM_TERRAINS] =
  216.     {
  217.     60, 60, 8, 8,        // BUILDING, RIVER, SWAMP, CRATER
  218.      1,  3, 8, 2,        // ROAD, FOREST, RUBBLE, GRASS,
  219.     60,  1, 8, 0, 1000    // HALFBUILDING, BOAT, DEEPSEA, REFBASE_T, PILLBOX_T
  220.     };
  221.  
  222. local void examine(CostPoint c)
  223.     {
  224.     MAP_X x = costarray_left + c.x;
  225.     MAP_Y y = costarray_top  + c.y;
  226.     TERRAIN t = raw_getmapcell(x,y);
  227.     long newcost = c.cost + route_square_costs[t & TERRAIN_MASK];
  228.     
  229.     if (t & TERRAIN_MINE)
  230.         {
  231.         // If clearing mines, cost is related to shells, else very high cost
  232.         if (clearmines && info->shells) newcost += 40-info->shells;
  233.         else newcost += 400;
  234.         }
  235.     
  236.     // Limit cost to stop it overflowing
  237.     if (newcost > MAX_COST) newcost = MAX_COST;
  238.     
  239.     if (c.x < COST_ARRAY_SIZE && c.y < COST_ARRAY_SIZE &&
  240.         squarecost[c.y][c.x] > newcost) { c.cost = newcost; setcost(c); }
  241.     }
  242.  
  243. local void make_costarray(MAP_X tx, MAP_Y ty)
  244.     {
  245.     int x,y;
  246.     u_short bld_cost = 1000;
  247.     
  248.     // Record info about this cost array
  249.     costarray_left = info->view_left;
  250.     costarray_top  = info->view_top;
  251.     costtarget_x   = tx;
  252.     costtarget_y   = ty;
  253.     costarray_time = TickCount();
  254.     costarray_boat = info->inboat;
  255.     
  256.     if (info->shells > 4) bld_cost = 60 - info->shells;
  257.     route_square_costs[BUILDING    ] = bld_cost;
  258.     route_square_costs[HALFBUILDING] = bld_cost;
  259.     route_square_costs[RIVER       ] = info->inboat ? 1 : 60;
  260.     route_square_costs[DEEPSEA     ] = info->inboat ? 8 : 1000;
  261.     
  262.     // And update tank local coordinates
  263.     ca_tankx = nearx - costarray_left;
  264.     ca_tanky = neary - costarray_top;
  265.  
  266.     // Initialize every square to max cost
  267.     for (y=0; y<COST_ARRAY_SIZE; y++)
  268.         for (x=0; x<COST_ARRAY_SIZE; x++) squarecost[y][x] = UNKNOWN_COST;
  269.     
  270.     // Start from target, with zero cost
  271.     if (tx < costarray_left) subtarget.x = 0; else subtarget.x = tx - costarray_left;
  272.     if (ty < costarray_top ) subtarget.y = 0; else subtarget.y = ty - costarray_top;
  273.     if (subtarget.x > COST_ARRAY_SIZE - 1) subtarget.x = COST_ARRAY_SIZE - 1;
  274.     if (subtarget.y > COST_ARRAY_SIZE - 1) subtarget.y = COST_ARRAY_SIZE - 1;
  275.     subtarget.cost = 0;
  276.     
  277.     // Check that tank falls within our visible region
  278.     if (ca_tankx >= COST_ARRAY_SIZE ||
  279.         ca_tanky >= COST_ARRAY_SIZE) return;
  280.  
  281.     heap_size = 0;
  282.     setcost(subtarget);
  283.     while (heap_size)
  284.         {
  285.         CostPoint c = removefromheap();
  286.         // Exit when we find a path to the tank
  287.         if (c.x == ca_tankx && c.y == ca_tanky) return;
  288.         
  289.         // otherwise, continue the search outwards
  290.         c.y--; examine(c); c.y++;
  291.         c.x++; examine(c); c.x--;
  292.         c.y++; examine(c); c.y--;
  293.         c.x--; examine(c);
  294.         }
  295.     }
  296.  
  297. local void find_best_route(MAP_X tx, MAP_Y ty)
  298.     {
  299.     int i, step = 1;
  300.     long bestx, besty;
  301.     u_short bestcost = MAX_COST;
  302.     static MAP_X nodiagx;        // Don't consider diagonal movement
  303.     static MAP_Y nodiagy;        // when on this square
  304.     
  305.     // If tank stuck in maze, or very close to a mine, then
  306.     // cutting corners with diagonal travel is not a good idea.
  307.     // Should proceed very cautiously, slowly, from square to square
  308.     if (info->tankobstructed || tank_close_to_mine)
  309.         { nodiagx = nearx; nodiagy = neary; }
  310.     if (nodiagx == nearx && nodiagy == neary) step = 2;
  311.  
  312.     ca_tankx = nearx - costarray_left;
  313.     ca_tanky = neary - costarray_top;
  314.     
  315.     // If array is old, or for the wrong target, make a new one
  316.     if ((long)TickCount() - costarray_time > 120 ||
  317.         costtarget_x != tx || costtarget_y != ty ||
  318.         costarray_boat != info->inboat) make_costarray(tx,ty);
  319.  
  320.     for (i=0; i<8; i+=step)
  321.         {
  322.         BYTE x = ca_tankx+surrounding.s.s8[i].x;
  323.         BYTE y = ca_tanky+surrounding.s.s8[i].y;
  324.         if (x < COST_ARRAY_SIZE &&
  325.             y < COST_ARRAY_SIZE && bestcost > squarecost[y][x])
  326.                 {
  327.                 bestcost = squarecost[y][x];
  328.                 bestx = ((WORLD_X)(x + costarray_left)<<8) + 0x80;
  329.                 besty = ((WORLD_Y)(y + costarray_top )<<8) + 0x80;
  330.                 }
  331.         }
  332.     i = aim(bestx - (long)info->tankx, besty - (long)info->tanky);
  333.     direction_votes[i + 8 >> 4 & 0xF] += 100;
  334.     }
  335.  
  336. // ****************************************************************************
  337.  
  338. // Code to think about moving objects on the screen
  339.  
  340. local void reset_boredom(u_long dist)
  341.     {
  342.     current_dist = dist;            // We are now this far from our target
  343.     current_attempt_time = (long)TickCount();
  344.                     // Keep our attention on this target for a while more
  345.     }
  346.  
  347. local u_long check_objects(void)
  348.     {
  349.     int i;
  350.     long tx, ty, dummy;
  351.     long *boredomtarget = &dummy;
  352.     ObjectInfo *ob;
  353.     ObjectInfo *nearest_tank = NULL;
  354.     ObjectInfo *nearest_pill = NULL, *nearest_dead   = NULL;
  355.     ObjectInfo *nearest_base = NULL, *nearest_refuel = NULL;
  356.     u_long dist, max_dist = aggressive ? MAX_VIS_RANGE : DEFENSIVE_RANGE;
  357.     u_long tankd = max_dist, pilld = max_dist, deadd = max_dist;
  358.     u_long based = max_dist, refueld = max_dist;
  359.     Boolean decided = FALSE;
  360.     BYTE wanted_shells = 20, useful_shells = 6;
  361.     
  362.     // If we are on a refuelling base then we want full shells, and we wait
  363.     // until we have got every last shell from the base, otherwise we
  364.     // will put up with as few as 20 for general trundling around the map
  365.     // and not be attracted by bases that have less than 6 shells to give.
  366.     if (getmapcellW(info->tankx, info->tanky) == REFBASE_T)
  367.         { wanted_shells = 40; useful_shells = 1; }
  368.     
  369.     if (info->base)    // If we have some information about a nearby base
  370.         {
  371.         // If base has nothing to give, mark it as "boring"
  372.         if (info->base_shells == 0 && info->base_armour <= MIN_BASE_ARMOUR)
  373.             baseboredom[info->base->idnum] = (long)TickCount() + boredomtime;
  374.         
  375.         // If interested in base, make it the "nearest_refuel"
  376.         if (info->shells < wanted_shells &&
  377.             info->base_shells >= useful_shells &&
  378.             info->base_armour > MIN_BASE_ARMOUR)
  379.                 {
  380.                 nearest_refuel = info->base;
  381.                 refueld = objectrange_to_me(nearest_refuel);
  382.                 }
  383.         }
  384.  
  385.     for (ob=&info->objects[0]; ob<&info->objects[info->num_objects]; ob++)
  386.         {
  387.         switch (ob->object)
  388.             {
  389.             case OBJECT_TANK:
  390.                 if((long)TickCount() - tankboredom[ob->idnum] > 0)
  391.                     if (ob->info & OBJECT_HOSTILE)
  392.                         if ((dist = objectrange_to_me(ob)) < tankd)
  393.                             { nearest_tank = ob; tankd = dist; }
  394.                 break;
  395.             case OBJECT_PILLBOX:
  396.                 if((long)TickCount() - pillboredom[ob->idnum] > 0)
  397.                     {
  398.                     if (ob->direction == 0)    // If pillbox dead
  399.                         {
  400.                         if ((dist = objectrange_to_me(ob)) < deadd)
  401.                             { nearest_dead = ob; deadd = dist; }
  402.                         }
  403.                     else if (ob->info & OBJECT_HOSTILE)
  404.                         {
  405.                         if ((dist = objectrange_to_me(ob)) < pilld)
  406.                             { nearest_pill = ob; pilld = dist; }
  407.                         }
  408.                     }
  409.                 break;
  410.             case OBJECT_REFBASE:
  411.                 if((long)TickCount() - baseboredom[ob->idnum] > 0)
  412.                     {
  413.                     if (ob->info & OBJECT_HOSTILE)
  414.                         {
  415.                         if (info->shells || (ob->info & OBJECT_NEUTRAL))
  416.                             if ((dist = objectrange_to_me(ob)) < based)
  417.                                 { nearest_base = ob; based = dist; }
  418.                         }
  419.                     else
  420.                         {
  421.                         if ((dist = objectrange_to_me(ob)) < refueld)
  422.                             { nearest_refuel = ob; refueld = dist; }
  423.                         }
  424.                     }
  425.                 break;
  426.             }
  427.         }
  428.  
  429.     // Special priority: don't leave man outside tank
  430.     if (info->man_status > 1 && info->manobstructed)
  431.         {
  432.         tx = info->man_x;
  433.         ty = info->man_y;
  434.         dist = findrange(tx, ty, info->tankx, info->tanky);
  435.         if (dist > 0x100) decided = TRUE;
  436.         }
  437.  
  438.     // First priority: hostile tanks/pillboxes which are within shooting range
  439.     if (!decided && (tankd < max_dist || pilld < max_dist))
  440.         {
  441.         Boolean wants_fight = info->shells > 5 &&
  442.                                 info->armour > incoming_shells + 2;
  443.         if (tankd < pilld)    // We have a hostile tank approaching
  444.             {
  445.             dist = tankd;
  446.             tx = nearest_tank->x;
  447.             ty = nearest_tank->y;
  448.             boredomtarget = &tankboredom[nearest_tank->idnum];
  449.             }
  450.         else                // Nearest thing is a hostile pillbox
  451.             {
  452.             dist = pilld;
  453.             tx = nearest_pill->x;
  454.             ty = nearest_pill->y;
  455.             boredomtarget = &pillboredom[nearest_pill->idnum];
  456.             // pacifists don't attack pillboxes
  457.             if (!aggressive) wants_fight = FALSE;
  458.             }
  459.         
  460.         // If within shooting range, must make a decision now.
  461.         // Otherwise if we want a fight, and there is no dead
  462.         // pillbox to pick up, attack this tank or pillbox.
  463.         if (dist < 0x800 || (wants_fight && deadd >= max_dist))
  464.             {
  465.             decided = TRUE;
  466.             if (!wants_fight) runaway = TRUE;
  467.             else if (dist < 0x800 && info->shells) shootit = TRUE;
  468.             }
  469.         }
  470.  
  471.     // Second priority: Pick up dead pillboxes
  472.     if (!decided && deadd < max_dist)
  473.         {
  474.         dist = deadd;
  475.         tx = nearest_dead->x;
  476.         ty = nearest_dead->y;
  477.         boredomtarget = &pillboredom[nearest_dead->idnum];
  478.         decided = TRUE;
  479.         }
  480.  
  481.     // Second priority: Capture some enemy bases
  482.     if (!decided && aggressive && based < max_dist)
  483.         {
  484.         dist = based;
  485.         tx = nearest_base->x;
  486.         ty = nearest_base->y;
  487.         boredomtarget = &baseboredom[nearest_base->idnum];
  488.         decided = TRUE;
  489.         if (dist < 0x800 && !(nearest_base->info & OBJECT_NEUTRAL) &&
  490.             info->shells) shootit = TRUE;
  491.         }
  492.  
  493.     // Third priority: Do we need to refuel?
  494.     if (!decided && refueld < max_dist &&
  495.         (info->shells < wanted_shells || info->armour < 8))
  496.         {
  497.         dist = refueld;
  498.         tx = nearest_refuel->x;
  499.         ty = nearest_refuel->y;
  500.         boredomtarget = &baseboredom[nearest_refuel->idnum];
  501.         // If we're on base; don't get bored
  502.         if (dist < 0x80)
  503.             {
  504.             reset_boredom(MAX_VIS_RANGE);
  505.             for (i=0; i<16; i++) target_distances[i] = 0;
  506.             }
  507.         decided = TRUE;
  508.         }
  509.  
  510.     // If not decided on anything, then there is nothing to be bored with
  511.     if (!decided)
  512.         {
  513.         if (!aggressive)    // If not aggressive, don't roam around
  514.             for (i=0; i<16; i++) target_distances[i] = 0;
  515.         reset_boredom(MAX_VIS_RANGE);
  516.         return(MAX_VIS_RANGE);
  517.         }
  518.     
  519.     // Still getting closer, so reset current_attempt_time
  520.     if (current_dist > dist) reset_boredom(dist);
  521.     else if ((long)TickCount() - current_attempt_time > ATTENTION_SPAN)
  522.         {
  523.         *boredomtarget = (long)TickCount() + boredomtime;
  524.         reset_boredom(MAX_VIS_RANGE);
  525.         }
  526.     
  527.     //if (info->tankobstructed && !info->shells) return(dist);
  528.     // Don't want this any more -- find_best_route works even if obstructed
  529.     
  530.     shoot_direction = aim(tx - (long)info->tankx, ty - (long)info->tanky);
  531.     shoot_dir_vote  = shoot_direction + 8 >> 4 & 0xF;
  532.     if (runaway)
  533.         {
  534.         reset_boredom(MAX_VIS_RANGE);    // Don't get bored while running away!
  535.         for (i=-4; i<=4; i++) direction_votes[shoot_dir_vote+i & 0xF] -= 20;
  536.         for (i=5; i<=11; i++) direction_votes[shoot_dir_vote+i & 0xF] += 20;
  537.         }
  538.     else
  539.         {
  540.         target_distances[shoot_dir_vote] = dist;
  541.         if (shootit || dist < 0x100)    // If shooting or VERY close to target
  542.             { direction_votes[shoot_dir_vote] += 100; reset_boredom(dist); }
  543.         else { find_best_route(tx>>8, ty>>8); return(0); }
  544.         // Return zero to override normal terrain evaluation
  545.         }
  546.     return(dist);
  547.     }
  548.  
  549. // ****************************************************************************
  550.  
  551. // Code to actively explore the map for things to do
  552.  
  553. local void setscout(void)
  554.     {
  555.     // Pick a new location to head towards
  556.     scoutx = 53 + 10 * (TickCount() & 15);
  557.     scouty = 53 + 10 * (TickCount()>>4 & 15);
  558.     }
  559.  
  560. local void scout(void)
  561.     {
  562.     u_long dist = findrange((WORLD_X)scoutx << 8, (WORLD_Y)scouty << 8,
  563.                                                 info->tankx, info->tanky);
  564.     // If still getting closer, reset current_attempt_time
  565.     if (current_dist > dist) reset_boredom(dist);
  566.     // else see if we should pick another target
  567.     else if ((long)TickCount() - current_attempt_time > ATTENTION_SPAN)
  568.         {
  569.         reset_boredom(MAX_VIS_RANGE);
  570.         setscout();
  571.         }
  572.     
  573.     if (nearx == scoutx && neary == scouty) setscout();
  574.  
  575.     find_best_route(scoutx, scouty);
  576.     
  577.     if (getmapcellM(costarray_left + subtarget.x, costarray_top + subtarget.y) == DEEPSEA)
  578.         setscout();
  579.     }
  580.  
  581. // ****************************************************************************
  582.  
  583. // Code to think about what kind of terrain we should try to get/stay on
  584. // Simple kind of insect behaviour -- just look at the nearby terrain and
  585. // decide which is nicest
  586.  
  587. local long terrain_desirability[NUM_TERRAINS] =
  588.     {
  589.     -10, -2, -1, -1,    // BUILDING, RIVER, SWAMP, CRATER
  590.     5, 0, -1, 0,        // ROAD, FOREST, RUBBLE, GRASS,
  591.     -10, 1, -100, 0, -10// HALFBUILDING, BOAT, DEEPSEA, REFBASE_T, PILLBOX_T
  592.     };
  593.  
  594. #define desirability(X,Y) \
  595. (terrain_desirability[getmapcellM((X),(Y))] + (checkmineM((X),(Y)) ? -500 : 0))
  596.  
  597. local void check_terrain(void)
  598.     {
  599.     int i;
  600.     long des = desirability(nearx, neary);
  601.     terrain_desirability[RIVER  ] = info->inboat ? 0 : -2;
  602.     terrain_desirability[DEEPSEA] = info->inboat ? -10 : -100;
  603.     for (i=0; i<8; i++)
  604.         {
  605.         long val = desirability(nearx+surrounding.s.s8[i].x,
  606.                                 neary+surrounding.s.s8[i].y) - des;
  607.         direction_votes[i*2-2 & 0xF] += val;
  608.         direction_votes[i*2-1 & 0xF] += val*2;
  609.         direction_votes[i*2        ] += val*5;
  610.         direction_votes[i*2+1      ] += val*2;
  611.         direction_votes[i*2+2 & 0xF] += val;
  612.         }
  613.  
  614.     for (i=0; i<16; i++)
  615.         {
  616.         long val = desirability(nearx+surrounding.s.s16[i].x,
  617.                                 neary+surrounding.s.s16[i].y) - des;
  618.         direction_votes[i-1 & 0xF] += val>>1;
  619.         direction_votes[i        ] += val;
  620.         direction_votes[i+1 & 0xF] += val>>1;
  621.         }
  622.  
  623.     // If on road, try to keep tank centred.
  624.     if (getmapcellM(nearx,neary) == ROAD)
  625.         {
  626.         Boolean go_down  = getmapcellW(info->tankx, info->tanky - 0x60) != ROAD;
  627.         Boolean go_up    = getmapcellW(info->tankx, info->tanky + 0x60) != ROAD;
  628.         Boolean go_right = getmapcellW(info->tankx - 0x60, info->tanky) != ROAD;
  629.         Boolean go_left  = getmapcellW(info->tankx + 0x60, info->tanky) != ROAD;
  630.         if (go_down + go_up + go_right + go_left == 1)
  631.             {
  632.             BYTE dir = go_right * 4 + go_down * 8 + go_left * 12;
  633.             for (i=-3; i<=3; i++) direction_votes[dir+i & 0xF] += 10;
  634.             }
  635.         }
  636.     }
  637.  
  638. // ****************************************************************************
  639.  
  640. // Code to organize voting about which direction we should be going in
  641.  
  642. local void cast_votes(void)
  643.     {
  644.     u_long target_distance;
  645.     BYTE d = info->direction + 8 >> 4 & 0xF;
  646.  
  647.     // If we are clearing mines, and we are dangerously close
  648.     // to a mine right now, should just try to get off it so
  649.     // that we get a chance for a safe shot at it.
  650.     if (clearmines && info->shells && tank_close_to_mine)
  651.         { check_terrain(); return; }
  652.  
  653.     // Bias towards continuing in the same direction
  654.     direction_votes[d-1 & 0xF] += 1;
  655.     direction_votes[d+0 & 0xF] += 2;
  656.     direction_votes[d+1 & 0xF] += 1;
  657.     direction_votes[d+7 & 0xF] -= 5;
  658.     direction_votes[d+8 & 0xF] -=10;
  659.     direction_votes[d+9 & 0xF] -= 5;
  660.  
  661.     target_distance = check_objects();
  662.     
  663.     // If only one square between us and the hostile,
  664.     // then terrain doesn't matter any more
  665.     if (target_distance < 0x180) return;
  666.     
  667.     // If nothing within sight, and aggressive, then explore
  668.     if (target_distance == MAX_VIS_RANGE && aggressive) scout();
  669.     else            // else just make local decision
  670.         {
  671.         if (still_on_boat) direction_votes[land_direction>>4] += 20;
  672.         check_terrain();
  673.         }
  674.     }
  675.  
  676. // ****************************************************************************
  677.  
  678. // Code to build bridges etc where necessary. Returns TRUE if decided to build
  679.  
  680. local Boolean decide_building(MAP_X x, MAP_Y y)
  681.     {
  682.     TERRAIN m = raw_getmapcell(x, y);
  683.     if (m & TERRAIN_MINE) return;
  684.     info->build->x = x;
  685.     info->build->y = y;
  686.     switch (m & TERRAIN_MASK)
  687.         {
  688.         case RIVER    :
  689.         case SWAMP    :
  690.         case CRATER   :
  691.         case RUBBLE   : if (info->trees >= 2)
  692.                             info->build->action = BUILDMODE_ROAD;
  693.                         break;
  694.         case FOREST   : if (info->trees < 40)
  695.                             info->build->action = BUILDMODE_FARM;
  696.                         break;
  697.         }
  698.     return(info->build->action != 0);
  699.     }
  700.  
  701. // ****************************************************************************
  702.  
  703. // Code to clear mines from in front of the tank
  704.  
  705. local MAP_X shotminex;        // Mine we have just shot at -- so ignore it
  706. local MAP_Y shotminey;        // when looking for other mines to shoot
  707. local long shotminetime;    // because it will be gone in a moment.
  708.  
  709. local MAP_X currentminex;    // The mine we plan to shoot at next
  710. local MAP_Y currentminey;
  711.  
  712. // Don't even consider trying to hit mines more than 0x60 from the centre.
  713. #define MINE_HIT_ERROR_LIMIT 0x60
  714.  
  715. local void minesweep(Boolean *foundmine, Boolean *doshoot)
  716.     {
  717.     char a, best_a;
  718.     short r, best_r = 0;
  719.     
  720.     // Want to try to hit mines as close to the centre as possible.
  721.     BYTE best_hit = MINE_HIT_ERROR_LIMIT;
  722.     
  723.     // If we already have something to shoot at, only worry about nearby mines
  724.     short max_range = shootit ? 6 : 12;
  725.     
  726.     if ((long)TickCount() - shotminetime > 60) shotminex = 0;
  727.     // After shooting at a mine, if, for some unknown reason, it is not
  728.     // gone within one second, then we should take another shot at it
  729.     
  730.     for (a = -32; a<=32; a+=8)    // sweep left and right of where tank is going
  731.         {
  732.         Boolean through_forest = FALSE;
  733.         for (r=2; r<max_range; r++)    // check up to max_range in front of tank
  734.             {
  735.             WORLD_X wx = info->tankx + sin(chosen_direction + a) * r;
  736.             WORLD_Y wy = info->tanky - cos(chosen_direction + a) * r;
  737.             MAP_X x = wx >> 8;        // x,y are square we are checking
  738.             MAP_Y y = wy >> 8;
  739.             TERRAIN m = raw_getmapcell(x, y);
  740.             TERRAIN t = m & TERRAIN_MASK;
  741.             
  742.             // Don't try to shoot through buildings or pillboxes
  743.             if (t == BUILDING || t == HALFBUILDING || t == PILLBOX_T) break;
  744.             
  745.             if (m & TERRAIN_MINE)        // If there is a mine on this square...
  746.                 {
  747.                 // Calculate how far from centre of square the shell will land
  748.                 BYTE dx = wx & 0xFF;    // dx,dy indicate position within square
  749.                 BYTE dy = wy & 0xFF;
  750.                 if (dx > 0x80) dx = dx-0x80; else dx = 0x80-dx;
  751.                 if (dy > 0x80) dy = dy-0x80; else dy = 0x80-dy;
  752.                 if (dx < dy) dx = dy;
  753.                 
  754.                 if (dx < MINE_HIT_ERROR_LIMIT)    // We want to shoot this
  755.                     {
  756.                     // Don't shoot the mine if we are so close it would damage us
  757.                     if ((x == tankleftx || x == tankrightx) &&
  758.                         (y == tanktopy  || y == tankbottomy)) continue;
  759.                     
  760.                     // If there is a mine very close to us, then take note of it
  761.                     // even if we can't (or don't want to) shoot it immediately.
  762.                     if (r<4) *foundmine = TRUE;
  763.                     
  764.                     // If we have already shot at the mine, ignore it (for now).
  765.                     if (x == shotminex && y == shotminey) continue;
  766.                     
  767.                     // See if this is our best chance at a clean hit
  768.                     if (best_hit > dx)
  769.                         {
  770.                         best_hit = dx;
  771.                         best_r = r;
  772.                         best_a = a;
  773.                         // If firing through forest, we need multiple shots
  774.                         // so don't assume shot will hit the mine first time
  775.                         if (through_forest) currentminex = 0;
  776.                         else { currentminex = x; currentminey = y; }
  777.                         }
  778.                     }
  779.                 }
  780.  
  781.             // If shot would pass through forest, make a note of the fact
  782.             if (t == FOREST) through_forest = TRUE;
  783.             
  784.             // If we are on a boat, we can't shoot past the shore
  785.             if (info->inboat && t != RIVER && t != DEEPSEA) break;
  786.             }
  787.         }
  788.  
  789.     if (best_r)
  790.         {
  791.         chosen_gunrange = best_r;
  792.         chosen_direction = chosen_direction + best_a;
  793.         *foundmine = *doshoot = TRUE;
  794.         }
  795.     }
  796.  
  797. // ****************************************************************************
  798.  
  799. // Code to count the votes and decide what action to take
  800.  
  801. local void count_votes(void)
  802.     {
  803.     int i, best = 0;
  804.     u_long target_distance;
  805.     u_long desired_dist = 0x20;    // Approach bases gently, and don't overshoot
  806.     char correction = 0;
  807.     Boolean panic = 
  808.             (getmapcellM(nearx,neary) == RIVER || info->inboat || runaway);
  809.     Boolean gofaster = FALSE, goslower = FALSE;
  810.     Boolean fullstop = FALSE, killmine = FALSE;
  811.     
  812.     for (i=1; i<16; i++)
  813.         if (direction_votes[best] < direction_votes[i]) best = i;
  814.  
  815.     if (best == shoot_dir_vote) chosen_direction = shoot_direction;
  816.     else { chosen_direction = best<<4; shootit = FALSE; }
  817.     target_distance = target_distances[best];
  818.  
  819.     // If we have some shells, and we are not on a mine,
  820.     // then think about clearing mines
  821.     if (clearmines && info->shells && !checkmineM(nearx,neary))
  822.         minesweep(&killmine, &shootit);
  823.  
  824.     if (target_distance > desired_dist || killmine)
  825.         correction = (char)(info->direction - chosen_direction);
  826.     
  827.     // Decide whether to turn, or possibly shoot
  828.     if      (correction < -9) setkey(keys, KEY_turnright);
  829.     else if (correction >  9) setkey(keys, KEY_turnleft);
  830.     else if (correction < -1) setkey(taps, KEY_turnright);
  831.     else if (correction >  1) setkey(taps, KEY_turnleft);
  832.     else if (shootit && info->gunrange == chosen_gunrange)
  833.         {        // OK, we are on target, and we want to shoot
  834.         if (!killmine) { setkey(keys, KEY_shoot); desired_dist = 0x680; }
  835.         else
  836.             {
  837.             // Want to make sure tank is not turning, adjusting range etc.
  838.             // to be sure we will hit the mine and not waste a shell
  839.             u_long testkeys = 1<<KEY_turnleft  | 1<<KEY_turnright
  840.                             | 1<<KEY_morerange | 1<<KEY_lessrange
  841.                             | 1<<KEY_shoot;
  842.             if ((testkeys & (last_keys | last_taps)) == 0)
  843.                 {
  844.                 // Tap shoot key, to fire ONE shot at the mine
  845.                 setkey(taps, KEY_shoot);
  846.                 shotminex = currentminex;
  847.                 shotminey = currentminey;
  848.                 shotminetime = TickCount();
  849.                 }
  850.             }
  851.         }
  852.     // Decide whether we should be building or farming here
  853.     if (CAN_DO_BUILDING) decide_building(nearx,neary);
  854.  
  855.     // Decide whether to speed up or slow down, based on whether the
  856.     // tank is facing in the right direction or not. If the tank is
  857.     // facing in the right direction, see if it would be helpful to
  858.     // build road or bridge in front of us
  859.     if (correction < -32 || correction > 32) goslower = TRUE;
  860.     else
  861.         {
  862.         MAP_X examinex = info->tankx + sin(info->direction) >> 8;
  863.         MAP_Y examiney = info->tanky - cos(info->direction) >> 8;
  864.         TERRAIN t = getmapcellM(examinex, examiney);
  865.         if (!info->inboat && t == DEEPSEA) fullstop = TRUE;
  866.         if (CAN_DO_BUILDING)
  867.             {
  868.             for (i=2; i<=3; i++)
  869.                 {
  870.                 if (decide_building(examinex,examiney)) break;
  871.                 examinex = info->tankx + sin(info->direction)*i >> 8;
  872.                 examiney = info->tanky - cos(info->direction)*i >> 8;
  873.                 }
  874.             }
  875.         
  876.         if (CAN_DO_BUILDING && placepills && info->carriedpills && info->trees >= 4)
  877.             {
  878.             // If carrying pillbox, drop it just behind the tank
  879.             // Can place pillbox on swamp, crater, road, rubble and grass
  880.             static Boolean can_build[NUM_TERRAINS] = { 0,0,1,1,1,0,1,1, 0,0,0,0,0 };
  881.             examinex = info->tankx - sin(info->direction)*2 >> 8;
  882.             examiney = info->tanky + cos(info->direction)*2 >> 8;
  883.             t = raw_getmapcell(examinex, examiney);
  884.             if (!(t & TERRAIN_MINE) && can_build[t & TERRAIN_MASK])
  885.                 {
  886.                 info->build->x = examinex;
  887.                 info->build->y = examiney;
  888.                 info->build->action = BUILDMODE_PBOX;
  889.                 }
  890.             }
  891.         
  892.         if (target_distance > desired_dist + (info->speed<<4)) gofaster = TRUE;        
  893.         if (target_distance < desired_dist + (info->speed<<3)) goslower = TRUE;
  894.         // If we want to go, but we are obstructed, then shoot our way out
  895.         if ((gofaster || panic) && info->tankobstructed && info->speed < 1)
  896.             setkey(keys, KEY_shoot);
  897.         }
  898.     
  899.     // And act on that decision
  900.     if (fullstop || killmine) setkey(keys, KEY_slower);
  901.     else if (panic) setkey(keys, KEY_faster);
  902.     else
  903.         {
  904.         if (gofaster) setkey(taps, KEY_faster);
  905.         if (goslower) setkey(keys, KEY_slower);
  906.         }
  907.  
  908.     // Aggressive tanks are vandalistic when running away
  909.     if (runaway && laymines) setkey(keys, KEY_dropmine);
  910.  
  911.     // Decide whether to adjust gun range
  912.     if      (info->gunrange > chosen_gunrange+2) setkey(keys, KEY_lessrange);
  913.     else if (info->gunrange < chosen_gunrange-2) setkey(keys, KEY_morerange);
  914.     else if (info->gunrange > chosen_gunrange  ) setkey(taps, KEY_lessrange);
  915.     else if (info->gunrange < chosen_gunrange  ) setkey(taps, KEY_morerange);
  916.     }
  917.  
  918. // ****************************************************************************
  919.  
  920. // Interface code to communicate with Bolo
  921.  
  922. local void brain_think(void)
  923.     {
  924.     int i;
  925.     ObjectInfo *ob;
  926.         
  927.     // Map square tank is on now
  928.     nearx = info->tankx >> 8;
  929.     neary = info->tanky >> 8;
  930.     
  931.     // Map squares tank is touching
  932.     tankleftx   = info->tankx - 0x80 >> 8;
  933.     tankrightx  = info->tankx + 0x80 >> 8;
  934.     tanktopy    = info->tanky - 0x80 >> 8;
  935.     tankbottomy = info->tanky + 0x80 >> 8;
  936.     
  937.     // See if tank is dangerously close to a mine
  938.     tank_close_to_mine =checkmineM(tankleftx,  tanktopy   ) ||
  939.                         checkmineM(tankrightx, tanktopy   ) ||
  940.                         checkmineM(tankleftx,  tankbottomy) ||
  941.                         checkmineM(tankrightx, tankbottomy);
  942.  
  943.     if (info->newtank)
  944.         {
  945.         reset_boredom(MAX_VIS_RANGE);
  946.         for (i=0; i<info->max_players;   i++) tankboredom[i] = ThinkStart;
  947.         for (i=0; i<info->max_pillboxes; i++) pillboredom[i] = ThinkStart;
  948.         for (i=0; i<info->max_refbases;  i++) baseboredom[i] = ThinkStart;
  949.         setscout();
  950.         still_on_boat  = TRUE;                    // Tank starts on boat
  951.         land_direction = info->direction;
  952.         }
  953.     
  954.     if (!info->inboat) still_on_boat = FALSE;    // Tank not on boat any more
  955.  
  956.     last_keys = keys;
  957.     last_taps = taps;
  958.     keys = taps = 0;
  959.     shootit = runaway = FALSE;
  960.     chosen_gunrange = MAXRANGE;
  961.  
  962.     // Count all shells, and count how many are coming towards us.
  963.     incoming_shells = flying_shells = 0;
  964.     for (ob=&info->objects[0]; ob<&info->objects[info->num_objects]; ob++)
  965.         if (ob->object == OBJECT_SHOT)
  966.             {
  967.             char dev = ob->direction - aim( (long)info->tankx - (long)ob->x,
  968.                                             (long)info->tanky - (long)ob->y );
  969.             if (dev > -32 && dev < 32) incoming_shells++;
  970.             flying_shells++;
  971.             }
  972.     
  973.     for (i=0; i<16; i++)
  974.         {
  975.         direction_votes[i] = 0;
  976.         target_distances[i] = 0xFFFFFFFF;
  977.         }
  978.  
  979.     cast_votes();
  980.     count_votes();
  981.  
  982.     *(info->holdkeys) = keys;
  983.     *(info->tapkeys ) = taps;
  984.     }
  985.  
  986. local Boolean brain_open(void)
  987.     {
  988.     int i;
  989.     //const u_char msg[] = "\pHello, I'll be your autopilot for today's drive";
  990.     tankboredom = (long *)NewPtr(sizeof(long) * info->max_players);
  991.     pillboredom = (long *)NewPtr(sizeof(long) * info->max_pillboxes);
  992.     baseboredom = (long *)NewPtr(sizeof(long) * info->max_refbases);
  993.     InsertMenu(MyMenu = GetMenu(MyMenuID), 0);
  994.     DrawMenuBar();
  995.     CheckItem(MyMenu, 1, aggressive);
  996.     CheckItem(MyMenu, 2, laymines  );
  997.     CheckItem(MyMenu, 3, clearmines);
  998.     CheckItem(MyMenu, 4, placepills);
  999.     
  1000.     // Initialize all the destination bits to zero
  1001.     // for (i=0; i<(info->max_players+31)/32; i++) info->messagedest[i] = 0;
  1002.     
  1003.     // for (i=0; i<info->max_players; i++) info->messagedest[i>>5] |= 1<<(i&31);
  1004.     // info->messagedest[info->player_number>>5] |= 1<<(info->player_number&31);
  1005.     
  1006.     // Copy the message
  1007.     // for (i=0; i<=msg[0]; i++) info->sendmessage[i] = msg[i];
  1008.     
  1009.     return(tankboredom && pillboredom && baseboredom);
  1010.     }
  1011.  
  1012. local void brain_close(void)
  1013.     {
  1014.     if (tankboredom) DisposPtr((Ptr)tankboredom);
  1015.     if (pillboredom) DisposPtr((Ptr)pillboredom);
  1016.     if (baseboredom) DisposPtr((Ptr)baseboredom);
  1017.     DeleteMenu(MyMenuID);
  1018.     ReleaseResource((Handle)MyMenu);
  1019.     DrawMenuBar();
  1020.     }
  1021.  
  1022. local short brain_menu(short item)
  1023.     {
  1024.     switch (item)
  1025.         {
  1026.         case 1 : CheckItem(MyMenu, 1, aggressive ^= 1); return(0);
  1027.         case 2 : CheckItem(MyMenu, 2, laymines   ^= 1); return(0);
  1028.         case 3 : CheckItem(MyMenu, 3, clearmines ^= 1); return(0);
  1029.         case 4 : CheckItem(MyMenu, 4, placepills ^= 1); return(0);
  1030.         default: return(-1);
  1031.         }
  1032.     }
  1033.  
  1034. pascal short main(const BrainInfo *braininfo)
  1035.     {
  1036.     if (braininfo->InfoVersion != CURRENT_BRAININFO_VERSION) return(-1);
  1037.  
  1038.     info = braininfo;    // copy parameter into global variable
  1039.     ThinkStart = TickCount();    // so we know how long we have been in this call
  1040.  
  1041.     switch (info->operation)
  1042.         {
  1043.         case BRAIN_OPEN  :    if (brain_open()) return(0);
  1044.                             else brain_close(); return(-1);
  1045.         case BRAIN_CLOSE :    brain_close(); return(0);
  1046.         case BRAIN_THINK :    brain_think(); return(0);
  1047.         case MyMenuID    :  return(brain_menu(info->menu_item));
  1048.         default          :  return(-1);
  1049.         }
  1050.     
  1051.     }
  1052.